/**
 * 地球半径（米）
 * @constant {number}
 */
const EARTH_RADIUS = 6370996.81

/**
 * 墨卡托坐标带宽数组
 * @constant {Array<number>}
 */
const MC_BAND = [12890594.86, 8362377.87, 5591021, 3481989.83, 1678043.12, 0]

/**
 * 经纬度坐标带宽数组
 * @constant {Array<number>}
 */
const LL_BAND = [75, 60, 45, 30, 15, 0]

/**
 * 墨卡托转经纬度的系数数组
 * @constant {Array<Array<number>>}
 */
const MC2LL = [
    [
        1.410526172116255e-8, 8.98305509648872e-6, -1.9939833816331,
        2.009824383106796e2, -1.872403703815547e2, 91.6087516669843,
        -23.38765649603339, 2.57121317296198, -0.03801003308653, 1.73379812e7,
    ],
    [
        -7.435856389565537e-9, 8.983055097726239e-6, -0.78625201886289,
        96.32687599759846, -1.85204757529826, -59.36935905485877, 47.40033549296737,
        -16.50741931063887, 2.28786674699375, 1.026014486e7,
    ],
    [
        -3.030883460898826e-8, 8.98305509983578e-6, 0.30071316287616,
        59.74293618442277, 7.357984074871, -25.38371002664745, 13.45380521110908,
        -3.29883767235584, 0.32710905363475, 6.85681737e6,
    ],
    [
        -1.981981304930552e-8, 8.983055099779535e-6, 0.03278182852591,
        40.31678527705744, 0.65659298677277, -4.44255534477492, 0.85341911805263,
        0.12923347998204, -0.04625736007561, 4.48277706e6,
    ],
    [
        3.09191371068437e-9, 8.983055096812155e-6, 0.00006995724062,
        23.10934304144901, -0.00023663490511, -0.6321817810242, -0.00663494467273,
        0.03430082397953, -0.00466043876332, 2.5551644e6,
    ],
    [
        2.890871144776878e-9, 8.983055095805407e-6, -0.00000003068298,
        7.47137025468032, -0.00000353937994, -0.02145144861037, -0.00001234426596,
        0.00010322952773, -0.00000323890364, 8.260885e5,
    ],
]
/**
 * 经纬度转墨卡托的系数数组
 * @constant {Array<Array<number>>}
 */
const LL2MC = [
    [
        -0.0015702102444, 1.113207020616939e5, 1.704480524535203e15,
        -1.033898737604234e16, 2.611266785660388e16, -3.51496691766537e16,
        2.659570071840392e16, -1.072501245418824e16, 1.800819912950474e15, 82.5,
    ],
    [
        // eslint-disable-next-line no-loss-of-precision
        8.277824516172526e-4, 1.113207020463578e5, 6.477955746671608e8,
        -4.082003173641316e9, 1.077490566351142e10, -1.517187553151559e10,
        1.205306533862167e10, -5.124939663577472e9, 9.133119359512032e8, 67.5,
    ],
    [
        0.00337398766765, 1.113207020202162e5, 4.481351045890365e6,
        -2.339375119931662e7, 7.968221547186455e7, -1.159649932797253e8,
        9.723671115602145e7, -4.366194633752821e7, 8.477230501135234e6, 52.5,
    ],
    [
        0.00220636496208, 1.113207020209128e5, 5.175186112841131e4,
        3.796837749470245e6, 9.920137397791013e5, -1.22195221711287e6,
        1.340652697009075e6, -6.209436990984312e5, 1.444169293806241e5, 37.5,
    ],
    [
        -3.441963504368392e-4, 1.113207020576856e5, 2.782353980772752e2,
        2.485758690035394e6, 6.070750963243378e3, 5.482118345352118e4,
        9.540606633304236e3, -2.71055326746645e3, 1.405483844121726e3, 22.5,
    ],
    [
        -3.218135878613132e-4, 1.113207020701615e5, 0.00369383431289,
        8.237256402795718e5, 0.46104986909093, 2.351343141331292e3,
        1.58060784298199, 8.77738589078284, 0.37238884252424, 7.45,
    ],
]

/**
 * 百度Web墨卡托投影类，用于百度地图坐标系转换
 * @class BaiduWebMercatorProjection
 */
class BaiduWebMercatorProjection {
    /**
     * 是否为WGS84坐标系
     * @type {boolean}
     */
    isWgs84
    
    /**
     * 构造函数
     */
    constructor() {
        this.isWgs84 = false
    }

    /**
     * 根据墨卡托坐标计算两点间距离
     * @param {Object} point1 - 第一个点的墨卡托坐标，格式为{lng, lat}
     * @param {Object} point2 - 第二个点的墨卡托坐标，格式为{lng, lat}
     * @returns {number} 两点间距离（米）
     */
    getDistanceByMC(point1, point2) {
        if (!point1 || !point2) {
            return 0
        }
        point1 = this.convertMC2LL(point1)
        if (!point1) {
            return 0
        }
        let x1 = this.toRadians(point1['lng'])
        let y1 = this.toRadians(point1['lat'])
        point2 = this.convertMC2LL(point2)
        if (!point2) {
            return 0
        }
        let x2 = this.toRadians(point2['lng'])
        let y2 = this.toRadians(point2['lat'])
        return this.getDistance(x1, x2, y1, y2)
    }

    /**
     * 根据经纬度坐标计算两点间距离
     * @param {Object} point1 - 第一个点的经纬度坐标，格式为{lng, lat}
     * @param {Object} point2 - 第二个点的经纬度坐标，格式为{lng, lat}
     * @returns {number} 两点间距离（米）
     */
    getDistanceByLL(point1, point2) {
        if (!point1 || !point2) {
            return 0
        }
        point1['lng'] = this.getLoop(point1['lng'], -180, 180)
        point1['lat'] = this.getRange(point1['lat'], -74, 74)
        point2['lng'] = this.getLoop(point2['lng'], -180, 180)
        point2['lat'] = this.getRange(point2['lat'], -74, 74)
        let x1 = this.toRadians(point1['lng'])
        let y1 = this.toRadians(point1['lat'])
        let x2 = this.toRadians(point2['lng'])
        let y2 = this.toRadians(point2['lat'])
        return this.getDistance(x1, x2, y1, y2)
    }

    /**
     * 平面直角坐标转换成经纬度坐标
     * @param {Object} point - 墨卡托坐标，格式为{lng, lat}
     * @returns {Object} 经纬度坐标，格式为{lng: string, lat: string}
     */
    convertMC2LL(point) {
        if (!point) {
            return { lng: 0, lat: 0 }
        }
        let lnglat = { lng: 0, lat: 0 }
        if (this.isWgs84) {
            lnglat.lng = (point.lng / 20037508.34) * 180;
            let mmy = (point.lat / 20037508.34) * 180;
            lnglat.lat =
                (180 / Math.PI) *
                (2 * Math.atan(Math.exp((mmy * Math.PI) / 180)) - Math.PI / 2);
            return {
                lng: lnglat['lng'].toFixed(6),
                lat: lnglat['lat'].toFixed(6),
            }
        }

        let temp = {
            lng: Math.abs(point['lng']),
            lat: Math.abs(point['lat']),
        }

        let factor = undefined
        for (let i = 0; i < MC_BAND.length; i++) {
            if (temp['lat'] >= MC_BAND[i]) {
                factor = MC2LL[i]
                break
            }
        }
        lnglat = this.convertor(point, factor)
        return {
            lng: lnglat['lng'].toFixed(6),
            lat: lnglat['lat'].toFixed(6),
        }
    }

    /**
     * 经纬度坐标转换成平面直角坐标
     * @param {Object} point - 经纬度坐标，格式为{lng, lat}
     * @returns {Object} 墨卡托坐标，格式为{lng: number, lat: number}
     */
    convertLL2MC(point) {
        if (!point) {
            return { lng: 0, lat: 0 }
        }
        if (
            point['lng'] > 180 ||
            point['lng'] < -180 ||
            point['lat'] > 90 ||
            point['lat'] < -90
        ) {
            return point
        }

        if (this.isWgs84) {
            let mercator = { lng: 0, lat: 0 }
            let earthRad = 6378137.0
            mercator.lng = ((point.lng * Math.PI) / 180) * earthRad
            let a = (point.lat * Math.PI) / 180
            mercator.lat =
                (earthRad / 2) * Math.log((1.0 + Math.sin(a)) / (1.0 - Math.sin(a)))

            return {
                lng: parseFloat(mercator['lng'].toFixed(2)),
                lat: parseFloat(mercator['lat'].toFixed(2)),
            }
        }

        point['lng'] = this.getLoop(point['lng'], -180, 180)
        point['lat'] = this.getRange(point['lat'], -74, 74)
        let temp = { lng: point['lng'], lat: point['lat'] }
        let factor = undefined
        for (let i = 0; i < LL_BAND.length; i++) {
            if (temp['lat'] >= LL_BAND[i]) {
                factor = LL2MC[i]
                break
            }
        }
        if (!factor) {
            for (let i = 0; i < LL_BAND.length; i++) {
                if (temp['lat'] <= -LL_BAND[i]) {
                    factor = LL2MC[i]
                    break
                }
            }
        }
        let mc = this.convertor(point, factor)
        return {
            lng: parseFloat(mc['lng'].toFixed(2)),
            lat: parseFloat(mc['lat'].toFixed(2)),
        }
    }

    /**
     * 坐标转换核心函数
     * @param {Object} fromPoint - 输入坐标，格式为{lng, lat}
     * @param {Array<number>} factor - 转换系数数组
     * @returns {Object} 转换后的坐标，格式为{lng: number, lat: number}
     */
    convertor(fromPoint, factor) {
        if (!fromPoint || !factor) {
            return { lng: 0, lat: 0 }
        }
        let x = factor[0] + factor[1] * Math.abs(fromPoint['lng'])
        let temp = Math.abs(fromPoint['lat']) / factor[9]
        let y =
            factor[2] +
            factor[3] * temp +
            factor[4] * temp * temp +
            factor[5] * temp * temp * temp +
            factor[6] * temp * temp * temp * temp +
            factor[7] * temp * temp * temp * temp * temp +
            factor[8] * temp * temp * temp * temp * temp * temp
        x *= fromPoint['lng'] < 0 ? -1 : 1
        y *= fromPoint['lat'] < 0 ? -1 : 1
        return {
            lng: x,
            lat: y,
        }
    }

    /**
     * 根据弧度计算两点间距离
     * @param {number} x1 - 第一个点的经度（弧度）
     * @param {number} x2 - 第二个点的经度（弧度）
     * @param {number} y1 - 第一个点的纬度（弧度）
     * @param {number} y2 - 第二个点的纬度（弧度）
     * @returns {number} 两点间距离（米）
     */
    getDistance(x1, x2, y1, y2) {
        return (
            EARTH_RADIUS *
            Math.acos(
                Math.sin(y1) * Math.sin(y2) +
                Math.cos(y1) * Math.cos(y2) * Math.cos(x2 - x1)
            )
        )
    }

    /**
     * 角度转弧度
     * @param {number} deg - 角度值
     * @returns {number} 弧度值
     */
    toRadians(deg) {
        return (Math.PI * deg) / 180
    }

    /**
     * 弧度转角度
     * @param {number} rad - 弧度值
     * @returns {number} 角度值
     */
    toDegrees(rad) {
        return (180 * rad) / Math.PI
    }

    /**
     * 限制值在指定范围内
     * @param {number} v - 输入值
     * @param {number} a - 最小值
     * @param {number} b - 最大值
     * @returns {number} 限制后的值
     */
    getRange(v, a, b) {
        if (a != null) {
            v = Math.max(v, a)
        }
        if (b != null) {
            v = Math.min(v, b)
        }
        return v
    }

    /**
     * 循环值在指定范围内
     * @param {number} v - 输入值
     * @param {number} a - 最小值
     * @param {number} b - 最大值
     * @returns {number} 循环后的值
     */
    getLoop(v, a, b) {
        while (v > b) {
            v -= b - a
        }
        while (v < a) {
            v += b - a
        }
        return v
    }

    /**
     * 经纬度转换为墨卡托坐标
     * @param {Object} point - 经纬度坐标，格式为{lng, lat}
     * @returns {Object} 墨卡托坐标，格式为{lng: number, lat: number}
     */
    lngLatToMercator(point) {
        return this.convertLL2MC(point)
    }

    /**
     * 经纬度转换为平面坐标
     * @param {Object} point - 经纬度坐标，格式为{lng, lat}
     * @returns {Object} 平面坐标，格式为{x: number, y: number}
     */
    lngLatToPoint(point) {
        let mercator = this.convertLL2MC(point)
        return {
            x: mercator['lng'],
            y: mercator['lat'],
        }
    }

    /**
     * 墨卡托变换至经纬度
     * @param {Object} point - 墨卡托坐标，格式为{lng, lat}
     * @returns {Object} 经纬度坐标，格式为{lng: string, lat: string}
     */
    mercatorToLngLat(point) {
        return this.convertMC2LL(point)
    }

    /**
     * 平面坐标转换为经纬度坐标
     * @param {Object} point - 平面坐标，格式为{x, y}
     * @returns {Object} 经纬度坐标，格式为{lng: string, lat: string}
     */
    pointToLngLat(point) {
        let mercator = { lng: point.x, lat: point.y }
        return this.convertMC2LL(mercator)
    }

    /**
     * 地理坐标转换至像素坐标
     * @param {Object} point - 地理坐标，格式为{lng, lat}
     * @param {number} zoom - 缩放级别
     * @param {Object} mapCenter - 地图中心点（墨卡托坐标），格式为{lng, lat}
     * @param {Object} mapSize - 地图容器大小，格式为{width, height}
     * @returns {Object|undefined} 像素坐标，格式为{x: number, y: number}，若输入无效则返回undefined
     */
    pointToPixel(point, zoom, mapCenter, mapSize) {
        if (!point) {
            return
        }
        point = this.lngLatToMercator(point)
        let zoomUnits = this.getZoomUnits(zoom)
        let x = Math.round(
            (point['lng'] - mapCenter['lng']) / zoomUnits + mapSize.width / 2
        )
        let y = Math.round(
            (mapCenter['lat'] - point['lat']) / zoomUnits + mapSize.height / 2
        )
        return { x, y }
    }

    /**
     * 像素坐标转换至地理坐标
     * @param {Object} pixel - 像素坐标，格式为{x, y}
     * @param {number} zoom - 缩放级别
     * @param {Object} mapCenter - 地图中心点（墨卡托坐标），格式为{lng, lat}
     * @param {Object} mapSize - 地图容器大小，格式为{width, height}
     * @returns {Object|undefined} 经纬度坐标，格式为{lng: string, lat: string}，若输入无效则返回undefined
     */
    pixelToPoint(pixel, zoom, mapCenter, mapSize) {
        if (!pixel) {
            return
        }
        let zoomUnits = this.getZoomUnits(zoom)
        let lng = mapCenter['lng'] + zoomUnits * (pixel.x - mapSize.width / 2)
        let lat = mapCenter['lat'] - zoomUnits * (pixel.y - mapSize.height / 2)
        let point = { lng, lat }
        return this.mercatorToLngLat(point)
    }

    /**
     * 获取指定缩放级别的单位
     * @param {number} zoom - 缩放级别
     * @returns {number} 单位值
     */
    getZoomUnits(zoom) {
        return Math.pow(2, 18 - zoom)
    }
}

export default BaiduWebMercatorProjection